/**
    \class QRoundabout
    \brief QGraphicsView dostosowany do naszych potrzeb.

    QRoundabout skupia w sobie informacje na temat logicznej budowy ronda: ilość gałęzi, ich charakterystyka i zależności między nimi, tory tramwajowe. Ponadto odpowiada za narysowanie ronda: oblicza niezbędne współrzędne na podstawie podanych ustawień.
 */
#include "qroundabout.h"
#include "pedestrian.h"
#include "tram.h"
#include <QStatusBar>
#include <QDebug>
#include <QTimer>
#include <QTime>
#include <QTimeLine>
#include <QWheelEvent>
#include <QMutex>
#include <cmath>

#define PI 3.14159265358979323846264338327950288419716939937510

/**
@brief Jedyny konstruktor

@param nLanes Liczba pasów na rondzie (nie dotyczy gałęzi
@param nBranches Liczba gałęzi
@param laneWidth Szerokość jednego pasa jezdni
@param radius Promień ronda; odległość od (0,0) do zewnętrznej krawędzi obręczy.
@param parent Dziedziczymy po QWidget, więc trzymamy się standardów.
*/
QRoundabout::QRoundabout(QWidget *parent, int nLanes, int nBranches, int laneWidth, int radius)
     : _radius(radius), _laneWidth(laneWidth),  _numLanes(nLanes), _numBranches(nBranches),
       _wantedScaling(1), _actualScaling(1), _numScheduledScalings(0), _scalingsTotal(1)
{
    this->setParent(parent);
    qreal fi = qreal(qreal(360) / qreal(nBranches));
    for (int i = 0; i < nBranches; ++i)
    {
        if (i % 2)
            _branches.append(new Branch(nLanes, entry, fi * i, _laneWidth));
        else
            _branches.append(new Branch(nLanes-1, outlet, fi * i, _laneWidth));
        _dependences.append(none);
        for (int j = 0; j < nBranches; ++j)
            _branches[i]->getTrafficDistribution()->append(0);
    }

    makeBasicTraffic();

    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    _scene = new QGraphicsScene;
    _scene->setSceneRect(-10000, -10000, 20000, 20000);
    _scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex);

    _backgroundColor = new QColor(0, 159, 0);

    setScene(_scene);
    setRenderHint(QPainter::Antialiasing);
    setBackgroundBrush(QBrush(*_backgroundColor, Qt::SolidPattern));
    setCacheMode(QGraphicsView::CacheBackground);
    setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    setDragMode(QGraphicsView::ScrollHandDrag);
    setOptimizationFlag(DontAdjustForAntialiasing);

    _scalingTimer = new QTimer(this);
}

void QRoundabout::generatePedestrian(int source)
{
    qreal fi = _branches[source]->getFi();
    qreal zebraWidth = _branches[source]->getWidth();

    if (_dependences[source] == glued)
    {
        int next = plusModulo(source, 1, _branches.size());
        fi = _branches[next]->getFi();  // jestem doklejony do następnego.
        zebraWidth += _branches[next]->getWidth(); // muszę zsumować szerokości moją i następnego
    }
    int prev = minusModulo(source, 1, _branches.size());
    if (_dependences[prev] == glued)
    { // poprzedni się do mnie dokleił - jego problem
        generatePedestrian(prev);
        return;
    }

    qreal dist = _radius;
    QPointF wektor(int(-dist * sin(fi*PI/180)), int(dist * cos(fi*PI/180)));
    qreal length = sqrt(pow(wektor.x(), 2) + pow(wektor.y(), 2));
    wektor /= length;    //tak, to tak działa

    // obracam o 90 st.
    qreal tmp = wektor.x();
    wektor.setX(-wektor.y());
    wektor.setY(tmp);

    wektor *= (zebraWidth + _laneWidth/1.5) / 2; // połowa szerokości jezdni + trochę, żeby piesi nie spawnowali się na jezdni;

    dist = _radius + qrand()%int(_laneWidth); // + losowa wartość mniejsza niż szerokość pasów (czyli lane Width)
    QPointF X(int(-dist * sin(fi*PI/180)), int(dist * cos(fi*PI/180))); // środek jezdni;
    dist = _radius + qrand()%int(_laneWidth); // + losowa wartość mniejsza niż szerokość pasów (czyli lane Width)
    QPointF Y(int(-dist * sin(fi*PI/180)), int(dist * cos(fi*PI/180))); // środek jezdni;


    QPointF startPoint(X + wektor);
    QPointF endPoint(Y - wektor);
    if (qrand()%2)
    {   // co drugi będzie chodził w drugą stronę.
        QPointF tmp(startPoint);
        startPoint = endPoint;
        endPoint = tmp;
    }

    qreal duration = zebraWidth/_laneWidth * 850 + qrand()%3000;  // długość trwania przejścia w linii prostej: 850ms na pas plus 0-3 sekundy
    qreal speed = zebraWidth / duration;  // to jest prędkość na jedną milisekundę
    /* 33,3 klatki to sekunda;
    1 klatka to 1000ms/33,3;

    jedna milisekunda to 33.3/1000 klatki

    mam prędkoś na milisekundę
    potrzebuję prędkość na klatkę
    zatem: */
    speed = speed * 33;

    Pedestrian *pedestrian = new Pedestrian(startPoint, endPoint, -_laneWidth/6, -_laneWidth/6, _laneWidth/3, _laneWidth/3, speed);
    scene()->addItem(pedestrian);
}

int QRoundabout::generateCar(int startingLane, int destLane, int from, int to, int rules)
{
    if ( _branches[from]->getDirection() != entry )
        qDebug() << "Car couldn't be created: from == outlet";
    if ( _branches[from]->getNumLanes()-1 < startingLane )
        qDebug() << "Car couldn't be created: from.numLanes-1 < startingLane";
    if ( _branches[to]->getDirection() != outlet)
        qDebug() << "Car couldn't be created: dest == entry";

    if (( _branches[from]->getDirection() != entry  ) ||
        ( _branches[from]->getNumLanes()-1 < startingLane ) ||
        ( _branches[to]->getDirection() != outlet))
        return -1;

    // to jest odległość, w której ma zostać stworzony pojazd
    qreal dist = _radius + 20*_laneWidth;
    qreal startFi = getBranchFi(from);

    QPointF spawnPoint((-dist * sin(double(startFi * PI/180))),
                       ( dist * cos(double(startFi * PI/180))));

    QPointF wektor((-dist * sin(double(startFi * PI/180))),
                   ( dist * cos(double(startFi * PI/180))));

    qreal length = sqrt(pow(wektor.x(), 2) + pow(wektor.y(), 2));
    wektor /= length;    // uzyskuję wektor jednostkowy równoległy do linii do centrum
    QPointF radiusVector = wektor;

    // obracam o 90 st.
    qreal tmp = wektor.x();
    wektor.setX(-wektor.y());
    wektor.setY(tmp);

    int next = plusModulo(from, 1, _branches.size());
    int prev = minusModulo(from, 1, _branches.size());
    // przesuwam na odpowiedni pas
    if (_dependences[from] == glued)
    {
        qreal middleLane = qreal(_branches[from]->getNumLanes() + _branches[next]->getNumLanes()) / 2.0;
        spawnPoint += ((startingLane - middleLane) * _laneWidth + _laneWidth/6 + _laneWidth/3) * wektor;
    }
    else
    {
        if (_dependences[prev] == glued)
        {
            qreal middleLane = qreal(_branches[from]->getNumLanes() + _branches[prev]->getNumLanes()) / 2.0;
            spawnPoint += ((startingLane - middleLane) * _laneWidth + _laneWidth/6 + _laneWidth/3) * wektor;
            spawnPoint += (_branches[prev]->getWidth()) * wektor;
        }
        else
        {
            qreal middleLane = qreal(_branches[from]->getNumLanes()) / 2.0;
            spawnPoint += ((startingLane - middleLane) * _laneWidth + _laneWidth/6 + _laneWidth/3) * wektor;
        }
    }

    QPainterPath spawnPath(spawnPoint);
    spawnPath.lineTo( spawnPoint + 2*_laneWidth * radiusVector );

    while ( _scene->items(spawnPath).size()  )  //  _scene->itemAt(spawnPoint) != 0)
    { // miejsce spawnu zablokowane
        spawnPoint += 2*_laneWidth * radiusVector;
        spawnPath.translate( 2*_laneWidth * radiusVector );

        if (QLineF(QPointF(0, 0), spawnPoint).length() > 10*_radius)
            return -1; // KOREK
    }

    _branchesCars.append(new Car(spawnPoint, startFi, _laneWidth, this, startingLane, to, from, destLane, rules));
    _scene->addItem(_branchesCars.last());
    connect(_branchesCars.last(), SIGNAL(iAmDestroyed()), parent()->parent()->parent(), SLOT(carDestroyed()));
    return 0;
}

void QRoundabout::drawBackground ( QPainter * painter, const QRectF & rect )
{
    //painter->setRenderHint(QPainter::Antialiasing, true);
    QBrush brush(QBrush(*_backgroundColor, Qt::SolidPattern));
    painter->setBrush(brush);
    painter->drawRect(_scene->sceneRect());
    drawConnections(painter);
    drawBranches(painter);
    drawRoundabout(painter);
    drawRails(painter);
    if (_barriersVisible)
        drawBarriers(painter);
    //showCoords(painter);
}
/**
    Funkcja odpowiedzialna za rysowanie torów. Jej głównym zadaniem jest obliczanie kątów odchyleń torów w zależności od aktualnego układu gałęzi (oraz zależności między nimi). Tutaj zaradzamy takim problemom jak np. tory między sklejonym gałęziami i tym podobne.
    Całe to liczenie jest w funkcji rysującej, aby możliwe było dynamicznie zmienianie układu ronda, bez konieczności usuwania całego obiektu QRoundabout i tworzenia go na nowo.
  */
void QRoundabout::drawRails(QPainter *painter)
{
    foreach(Rails *rails, _rails)
    {
        /* trza obliczyć średnie fi między drogami startowymi
        oraz końcowymi. Trzeba uważać na wariant glued. */
        int startLeft, startRight, endLeft, endRight;

        startRight = rails->getFrom();
        endRight = rails->getTo();
        startLeft = plusModulo(rails->getFrom(), 1, _branches.size());
        endLeft = plusModulo(rails->getTo(), 1, _branches.size());

        if (_dependences[startLeft] == glued)
            startLeft = plusModulo(startLeft, 1, _branches.size());
        if (_dependences[endLeft] == glued)
            endLeft = plusModulo(endLeft, 1, _branches.size());

        qreal sumaStart = _branches.at(startLeft)->getFi() + _branches.at(startRight)->getFi();
        if ((_branches.at(startLeft)->getFi() < _branches.at(startRight)->getFi()))
            sumaStart -= 360;// - sumaStart;

        qreal sumaEnd = _branches.at(endLeft)->getFi() + _branches.at(endRight)->getFi();
        if ((_branches.at(endLeft)->getFi() < _branches.at(endRight)->getFi()))
            sumaEnd -= 360;// - sumaEnd;

        qreal startFi = (sumaStart ) / 2;
        qreal endFi = (sumaEnd) / 2;

        // whatif onroad? trza dać startFi takie jak droga po prawej etc
        if (rails->getFromRailBed() == onroad)
            startFi = _branches.at(startRight)->getFi();
        if (rails->getToRailBed() == onroad)
            endFi = _branches.at(endRight)->getFi();

        if (_dependences.at(rails->getFrom()) == glued)
        {
            rails->setFromRailBed(onroad);
            startFi = _branches.at(plusModulo(startRight, 1, _branches.size()))->getFi();
        }
        if (_dependences.at(rails->getTo()) == glued)
        {
            rails->setToRailBed(onroad);
            endFi = _branches.at(plusModulo(endRight, 1, _branches.size()))->getFi();
        }
        rails->draw(painter, startFi, endFi, _radius - getRimWidth() - _laneWidth/3);
    }
}

/**
@brief Rysowanie ronda bez gałęzi.

Wszystkie dane potrzebne do rysowania pobierane są z naszej instancji klasy. Najpierw rysowane jest czarne koło, potem w środku drugie o kolorze tła. Następnie białą przerywaną linią rysujemy linie oddzielającą pasy.
Wykorzystane zmienne:
@see _backgroundColor
@see _radius
@see _numLanes
@see _laneWidth
@see _painter
*/
void QRoundabout::drawRoundabout(QPainter *painter)
{
    QBrush *brush = new QBrush();
    brush->setStyle(Qt::SolidPattern);
    brush->setColor(QColor(0,0,0));     // czarny kolor


    painter->setBrush(*brush);
    painter->setPen(Qt::NoPen);
    /* Mamy promień. pkt startowy (lewy górny róg) to (-r, -r).
     wielkość kwadratu to (2r, 2r)   */

    painter->drawEllipse(QRectF(-_radius, -_radius, 2 * _radius, 2 * _radius));  // rysuje czarne kolo. Uzależnić od
    painter->setBrush(_backgroundColor->toRgb());    // zmiena koloru na zielony (tlo)

    /* teraz, trzeba policzyć jak gruba ma być obręcz ronda. Grubość = ilość pasów * ich szerokość.
        wtedy punkt startowy kwadratu dla zielonego placyku to:
        x= r - g
        (-x, -x).
        Wielkość tego kwadratu to (2x, 2x)        */

    qreal greenRadius = _radius - (_numLanes * _laneWidth);
    painter->drawEllipse(QRectF(-greenRadius, -greenRadius, 2 * greenRadius, 2 * greenRadius));  // zielony placyk

    /* białe linie rozdzielające pasy
    jest ich ilość_pasów - 1. To okręgi o promieniach:
       (r - (nr_linii * szerokosc pasa))  */

    qreal lineRadius = 0;
    painter->setBrush(Qt::NoBrush);        // bez wypełnienia
    painter->setPen(QPen(Qt::white, 2, Qt::DashLine, Qt::FlatCap, Qt::RoundJoin)); // biala przerywana linia

    for (int i = 1; i < _numLanes; i++)
    {
        lineRadius = _radius - i *_laneWidth;
        painter->drawEllipse(QRectF(-lineRadius, -lineRadius, 2*lineRadius, 2 * lineRadius));
    }
}

/**
@brief Rysowanie gałęzi.

Tak jak w przypadku torów, każda z gałęzi ma własną metodę draw(), którą wywołujemy z odpowiedniego obiektu. Nie potrzebujemy obliczać teraz niczego obliczać - wystarczy wywołać funkcja dla każdej z gałęzi.
Osobno są traktowane gałęzie sklejone - do ich rysowania wywoływana jest przeciążona metoda draw, której przekazujemy z kim ma zostać sklejona dana gałąź.
@see _dependences
@see types.h
*/
void QRoundabout::drawBranches(QPainter *painter)
{
    /* zanim narysuje normalne ulice, to najpierw trzeba narysować połączenia
        bo to specyficzna sprawa: rysuje dwa wycinki koła, wiec mógłbym przesłonić
        coś.   */
/** Określa odkąd ma się rysować gałąź (chodzi o odległość od centrum ronda) */
    //int y = _radius - (_laneWidth) + 1;
    int j;
    for (int i = 0; i < _numBranches; i++)
    {
        if (_dependences.at(i) == glued)
        {
            j = plusModulo(i, 1, _dependences.size());
            _branches[i]->draw(painter, _radius, _branches[j]);
            i = i + 1;
            continue;
            // zwiększam i o 2 , bo  dwie sklejone gałęzie już są.
        }
        // rysuje zwykle
        _branches[i]->draw(painter, _radius);
    }
}

/**
@brief Rysowanie połączeń między gałęziami w stanie connected.

Następuje przez narysowanie dwóch wycinków koła, jeden na drugim. Aby to zrobić, najpierw pobieram odpowiednie wartości odchyleń.
@see _dependences
@see types.h
*/
void QRoundabout::drawConnections(QPainter *painter)
{
    int next;
    qreal startFi, endFi;
    for (int i = 0; i < _numBranches; i++)
    {    
        if (_dependences.at(i) == connected)
        {
            /* i==0 ->  łączę 0 i 1. Zero nie jest sklejone , bo wtedy dep[0]=glued
            zatem musze sprawdzic czy i+1 jest sklejone. Jesli tak, to endFi wezme z i+2 */
            next = plusModulo(i, 1, _dependences.size());

            startFi = getBranchFi(i);
            endFi = getBranchFi(next);

            /* teraz rysujemy sobie arcPie, w prostokącie obejmującym całe
                rondo + dwa dodatkowe pasy (jeden przerwy, drugi normalny)            */
            QBrush brush;
            int difference = minusModulo(int(endFi), int(startFi), 360);

            brush.setStyle(Qt::SolidPattern);
            brush.setColor(QColor(0,0,0));
            painter->setBrush(brush);
            painter->setPen(Qt::NoPen);
            painter->drawPie(QRectF((_radius + 2*_laneWidth),
                                    (_radius + 2*_laneWidth),
                                    -(2*_radius + 4*_laneWidth),
                                    -(2*_radius + 4*_laneWidth)), int((-90 - startFi)* 16), -16*difference);
            brush.setColor(QColor(0, 159, 0));
            painter->setBrush(brush);
            painter->drawPie(QRectF(_radius + _laneWidth,
                                    _radius + _laneWidth,
                                    -(2*_radius + 2*_laneWidth),
                                    -(2*_radius + 2*_laneWidth)), int((-90 - startFi)* 16), -16*difference);
        }
    }
}

/**
@brief Rysuje współrzędne.

Funkcja pomagająca przy orientowaniu się na scenie, rysuje wartość współrzędnych w węzłach siatki o długości jednostki 50 pikseli.
*/
void QRoundabout::showCoords(QPainter *painter)
{
    for (int x = -200; x < 200; x = x + 50)
        for (int y = -200; y < 200; y = y + 50)
            painter->drawText(x, y, QString::number(x) + ", " + QString::number(y));
}

/** @brief Oblicza pozycje barier i inicjalizuje je */
void QRoundabout::computeBarriers()
{
/** barriery WJAZDU na rondo. Musi być po jednej dla każdej gałęzi
    typu entry. Musi być DOKŁADNIE na szerokości gałęzi, nie może wystawać.
    Musi rozciągać się na wszystkie pasy.

    Algorytm obliczenia punktów bariery:
    oznaczam O = (0,0). znasz wektor OP. podziel go przez jego długość,
    pomnóż przez x, obróć o 90 stopni  (w dowolną stronę).
    Wynik oznaczam A. A = A * pół szerokości jezdni.
    Teraz, Twoje szukane punkty, to P+A i P-A.

    obracanie o 90 stopni można łatwo zrealizować macierzą obrotu, patrz np. :
    http://www.daktik.rubikon.pl/wzory/wzory_trygonometria_macierz_obrotu.htm    */
    int i = -1;
    foreach(Branch *branch, _branches)
    {
        i++;
        if (_dependences[i] == glued )
            continue; // nie ma branch'a tam gdzie myślimy (getFi), będzie natomiast przy następnej

        // obliczanie wektora OP (od (o,o) do początku jezdni.
        qreal dist = _radius;

        QPointF X(-dist * sin(double(branch->getFi()*PI/180)), dist * cos(double(branch->getFi()*PI/180)));
        QPointF wektor(-dist * sin(double(branch->getFi()*PI/180)), dist * cos(double(branch->getFi()*PI/180)));
        qreal length = sqrt(pow(wektor.x(), 2) + pow(wektor.y(), 2));
        wektor /= length;    //tak, to tak działa

        // obracam o 90 st.
        qreal tmp = wektor.x();
        wektor.setX(-wektor.y());
        wektor.setY(tmp);

        int poprzednia = minusModulo(i, 1, _branches.size());

        if (_dependences[poprzednia] != glued)
        {
            if (branch->getDirection() == outlet)
                continue;
            wektor *= branch->getWidth() / 2;
        }
        else
        {
            if ((branch->getDirection() == outlet) && (_branches[poprzednia]->getDirection() == entry))
            {
                /* Przesuwam punkt X w stronę przyklejonej do mnie gałęzi
                // aha, ja się posługuję punktem X jako środkiem jezdni, zatem:*/
                X = X - wektor * (branch->getWidth() / 2);
                wektor *= _branches[poprzednia]->getWidth() / 2;
            }
            else if ((branch->getDirection() == entry) && (_branches[poprzednia]->getDirection() == outlet))
            {
                X = X + wektor * (branch->getWidth() / 2);  // przesuwam tylko na siebie
                wektor *= _branches[poprzednia]->getWidth() / 2;
            }
            else if ((branch->getDirection() == outlet) && (_branches[poprzednia]->getDirection() == outlet))
                continue;
            else // both are entries
                wektor *= (branch->getWidth() + _branches[poprzednia]->getWidth()) / 2;
         }

         int prev = minusModulo(i, 1, _branches.size());
         int nrBranch = i;
         if (_dependences[prev] == glued)
            nrBranch = prev;
        _entryBarriers.append(new Barrier(X + wektor, X - wektor, enterroundabout, nrBranch));
    }

    /** WYJAZDY (czy ZJAZDY)
      1) Znajduję gałąź, która jest outletem.
      2) Liczę jej punkt środkowy i liczę sobie wektor jednostkowy równoległy do gałęzi.
      3) Ustawiam wektor, aby zaczynał się w punkcie środkowym jezdni.
      4) Mnożę go przez szerokość obręczy ronda
      5) przesuwam ten wektor o pół szerokości gałęzi counterclokcwise.

      alternatywnie: rysuję linię od (0,0), do prawej krawędzi zjazdu, czyli jak przy zmianie pasa
          jeszcze inaczej: narysować się pionowo (zero stopni) i obrócić się o fi!
      */
    i = -1;
    foreach(Branch *branch, _branches)
    {
        i++;
        if (_dependences[i] == glued )
            continue; // nie ma branch'a tam gdzie myślimy (getFi), będzie natomiast przy następnej

        // X to środek gałęzi przy styku z rondem.
        qreal dist = _radius;
        QPointF X(-dist * sin(double(branch->getFi()*PI/180)), dist * cos(double(branch->getFi()*PI/180)));
        QPointF wektor(-dist * sin(double(branch->getFi()*PI/180)), dist * cos(double(branch->getFi()*PI/180)));
        qreal length = sqrt(pow(wektor.x(), 2) + pow(wektor.y(), 2));
        wektor /= length;    //tak, to tak działa
        // (x, wektor) to linia od środka jezdni, w stronę ronda
        QLineF barrier(X, wektor);
        barrier.setLength(getRadius()); // 4

        qreal tmp = wektor.x();
        wektor.setX(-wektor.y());
        wektor.setY(tmp);
        // od tej pory wektor wskaże mi kierunek prostopadły do gałęzi.

        int poprzednia = i-1;
        if (poprzednia < 0)
            poprzednia = _branches.size() - 1;

        if (_dependences[poprzednia] != glued)  // jestem sam
        {
            if (branch->getDirection() == entry)  // i jestem wjazdem;
                continue;
            // jestem samotnym zjazdem - ALRIGHT!
            barrier.translate(wektor * (/*_laneWidth/3 + */branch->getWidth() / 2)); // 5
        }
        else
        {
            // ja jestem zjazdem, trza przesunąć barrier w moją stronę o połowę naszej szerokości
            if ((branch->getDirection() == outlet) && (_branches[poprzednia]->getDirection() == entry))
                barrier.translate( wektor * ((_branches[poprzednia]->getWidth() + branch->getWidth())/ 2)  );
            else if ((branch->getDirection() == entry) && (_branches[poprzednia]->getDirection() == outlet))
            {
                // przypadek oryginalny, bo.. nie trzeba nic przesuwać
                // taki przypadek nie powinien istnieć w ruchu prawostronnym
//                barrier.translate( wektor * (_laneWidth/3) );
            }
            else if ((branch->getDirection() == entry) && (_branches[poprzednia]->getDirection() == entry))
                continue;
            else // both are outlets
                barrier.translate(wektor * (/*_laneWidth/3 +*/ branch->getWidth() + _branches[poprzednia]->getWidth())); // 5
         }
        _outletBarriers.append(new Barrier(barrier, exitroundabout, i));
    }

    /// Teraz bariery ZMIAN PASA. Będą na wysokości każdej z gałęzi
    i = -1;
    foreach(Branch *branch, _branches)
    {
        i++;
//        if (_dependences[i] == glued )
//            continue; // nie ma branch'a tam gdzie myślimy (getFi), będzie natomiast przy następnej

        // X to środek gałęzi przy styku z rondem.
        int dist = _radius - _laneWidth;
        double fi;
        fi = getBranchFi(i) - 15;// branch->getFi() - 15;  // eksperyment
        /* TODO: powyższa wartość(-15) być może powinna być konfigurowalna w opcjach
           fajnie by wyglądało gdyby można było zaznaczyć tickiem 'rysuj bariery' a potem
           płynnie sobie je poprzesuwać. */
        if (_dependences[i] == glued )
            fi -= 5; // żeby nie pokrywała się z barierą od kolegi do którego jestem przyklejony

        QPointF X(int(-dist * sin(double(fi*PI/180))), int(dist * cos(double(fi*PI/180))));
        QPointF wektor(int(-dist * sin(double(fi*PI/180))), int(dist * cos(double(fi*PI/180))));
        qreal length = sqrt(pow(wektor.x(), 2) + pow(wektor.y(), 2));
        wektor /= length;    //tak, to tak działa
        // (x, wektor) to linia od środka jezdni, w stronę ronda
        QLineF barrier(X, wektor);
        barrier.setLength( (_numLanes) * _laneWidth);  // trochę szersze, żeby przesunięcie nie wypchnęło ich poza jezdnię

        qreal tmp = wektor.x();
        wektor.setX(-wektor.y());
        wektor.setY(tmp);
        // od tej pory wektor wskaże mi kierunek prostopadły do gałęzi.

        int poprzednia = minusModulo(i, 1, _branches.size());

        if (_dependences[poprzednia] != glued)  // jestem sam
            barrier.translate(0 * wektor * (branch->getWidth() / 2));
        else
            barrier.translate( 0 * wektor * ( (_branches[poprzednia]->getWidth() + branch->getWidth()) / 2)  );
        
        _changeLaneBarriers.append(new Barrier(barrier, exitroundabout, i));
    }

    /** bariery SKRÓTÓW. Są tam, gdzie dep[i-1] == conn
        w odległości radius + 2*laneWidth */
    i = -1;
    foreach(Branch *branch, _branches)
    {
        i++;
        if ((_dependences[minusModulo(i, 1, _branches.size())] != connected))
            continue;

        qreal fi = getBranchFi(i);
        
        // X to środek gałęzi przy styku z rondem.
        int dist = _radius +  2 * _laneWidth;
        QPointF X(int(-dist * sin(double(fi * PI/180.0))), int(dist * cos(double(fi * PI/180.0))));
        QPointF wektor(int(-dist * sin(double(fi * PI/180.0))), int(dist * cos(double(fi * PI/180.0))));
        qreal length = sqrt(pow(wektor.x(), 2) + pow(wektor.y(), 2));
        wektor /= length;    //tak, to tak działa

        // obracam o 90 st.
        qreal tmp = wektor.x();
        wektor.setX(-wektor.y());
        wektor.setY(tmp);

        if (_dependences[i] == glued)
            X = X - wektor * (branch->getWidth() / 2);
        wektor *= _laneWidth;

        _shortcutBarriers.append(new Barrier(X + wektor, X - wektor, shortcut));
    }

    /** bariery KOŃCA SKRÓTÓW. Są tam, gdzie dep[i] == conn
        w odległości radius + 2*laneWidth */
    i = -1;
    foreach(Branch *branch, _branches)
    {
        i++;
        if ((_dependences[i] != connected))
            continue;

        qreal fi;
        if (_dependences[i] == glued)
            fi = _branches.at(plusModulo(i, 1, _branches.size()))->getFi();
        else
            fi = _branches.at(i)->getFi();

        // X to środek gałęzi przy styku z rondem.
        int dist = _radius +  2 * _laneWidth;
        QPointF X(int(-dist * sin(double(fi * PI/180.0))), int(dist * cos(double(fi * PI/180.0))));
        QPointF wektor(int(-dist * sin(double(fi * PI/180.0))), int(dist * cos(double(fi * PI/180.0))));
        qreal length = sqrt(pow(wektor.x(), 2) + pow(wektor.y(), 2));
        wektor /= length;    //tak, to tak działa

        // (x, wektor) to linia od środka jezdni, w stronę ronda
        QLineF barrier(X, wektor);
        barrier.setLength(_laneWidth);

        qreal tmp = wektor.x();
        wektor.setX(-wektor.y());
        wektor.setY(tmp);
        // od tej pory wektor wskaże mi kierunek prostopadły do gałęzi.

        int prev = minusModulo(i, 1, _branches.size());
        if (_dependences[prev] == glued)
            barrier.translate(wektor * ((_branches[prev]->getWidth() + branch->getWidth())/ 2)  );
        else
            barrier.translate(wektor * (branch->getWidth() / 2));

        _shortcutEndBarriers.append(new Barrier(barrier, shortcutend));
    }
}

/** \brief Dodaje obiekty barier do sceny
    \param visible Określa, czy obiekty powinny być widoczne, czy nie. UWAGA! Same obiekty powinny być niewidoczne, inaczej samochody wezmą je za fizyczne obiekty (tzn. uznają bariery za inne samochody). Jeżeli chcesz, by bariery były widoczne podczas symulacji, ustaw pole _barriersVisible na true (pozwoli to na wykonanie funkcji drawBarriers() w drawBackground()).
  */
void QRoundabout::addBarriers(bool visible)
{
    foreach(Barrier *barrier, _entryBarriers)
    {
        barrier->setPen(QPen(Qt::red));
        barrier->setVisible(visible);
        _scene->addItem(barrier);
    }
    foreach(Barrier *barrier, _outletBarriers)
    {
        barrier->setPen(QPen(Qt::yellow));
        barrier->setVisible(visible);
        _scene->addItem(barrier);
    }
    foreach(Barrier *barrier, _changeLaneBarriers)
    {
        barrier->setPen(QPen(Qt::cyan));
        barrier->setVisible(visible);
        _scene->addItem(barrier);
    }
    foreach(Barrier *barrier, _shortcutBarriers)
    {
        barrier->setPen(QPen(Qt::green));
        barrier->setVisible(visible);
        _scene->addItem(barrier);
    }
    foreach(Barrier *barrier, _shortcutEndBarriers)
    {
        barrier->setPen(QPen(Qt::green));
        barrier->setVisible(visible);
        _scene->addItem(barrier);
    }
}

/*! \brief Rysuje linie w miejscu obiektów barier.

    Jako że same obiekty barier powinny być niewidoczne dla normalnego ruchu na rondzie (składowa visible w QGraphicsItem ustawiona na false), potrzebujemy tej funkcji, aby móc zobaczyć bariery bez paraliżowania ruchu.
    Legenda kolorów:
    <ol>
    <li> czerwony:  bariery wjazdu na rondo
    <li> żółty:     bariery zjazdu z ronda
    <li> turkusowy: bariery zmian pasa
    <li> zielony:   bariery początku i końca skrótów.
    </ol>
    Oczywiście użyte kolory nie mają żadnego znaczenia i służą jedynie lepszej wizualizacji.
    \see types.h::BarrierType
  */
void QRoundabout::drawBarriers(QPainter *painter)
{
    QPen pen;
    pen.setColor(Qt::red);
    pen.setStyle(Qt::SolidLine);
    painter->setPen(pen);
    foreach(Barrier *barrier, _entryBarriers)
        painter->drawLine(barrier->line());

    pen.setColor(Qt::yellow);
    painter->setPen(pen);
    foreach(Barrier *barrier, _outletBarriers)
       painter->drawLine(barrier->line());

    pen.setColor(Qt::cyan);
    painter->setPen(pen);
    foreach(Barrier *barrier, _changeLaneBarriers)
       painter->drawLine(barrier->line());

    pen.setColor(Qt::green);
    painter->setPen(pen);
    foreach(Barrier *barrier, _shortcutBarriers)
       painter->drawLine(barrier->line());

    pen.setColor(Qt::green);
    painter->setPen(pen);
    foreach(Barrier *barrier, _shortcutEndBarriers)
       painter->drawLine(barrier->line());
}

void QRoundabout::mousePressEvent(QMouseEvent *event)
{
    _numScheduledScalings = 0;
    if (_rails.size() > 0)
    {
        Tram *t = new Tram(_rails.at(0), this, 200);
        _scene->addItem(t);
    }
    QGraphicsView::mousePressEvent(event);
}

void QRoundabout::wheelEvent ( QWheelEvent * event )
{
    int numDegrees = event->delta() / 8;
    int numSteps = numDegrees / 15;  // ilość "skoków" kółka;
    _numScheduledScalings += numSteps;
    if (_numScheduledScalings * numSteps < 0)  // jeśli są różnych znaków
        _numScheduledScalings = numSteps;

    QTimeLine *anim = new QTimeLine(350, this);
    anim->setUpdateInterval(20);

    connect(anim, SIGNAL(valueChanged(qreal)), SLOT(scalingTime(qreal)));
    connect(anim, SIGNAL(finished()), SLOT(animFinished()));
    anim->start();
}

void QRoundabout::scalingTime(qreal x)
{
    _scalingMutex.lock();
    qreal factor = 1.0 + qreal(_numScheduledScalings) / 300.0;

    if ((_scalingsTotal * factor < 0.1) || (_scalingsTotal * factor > 20))
    {
        QTimeLine *anim = (QTimeLine *)sender();
        anim->stop();
        _scalingMutex.unlock();
        return;
    }

    scale(factor, factor);
    _scalingsTotal *= factor;
    _scalingMutex.unlock();
}

void QRoundabout::animFinished()
{
    if (_numScheduledScalings > 0)
        _numScheduledScalings--;
    else
        _numScheduledScalings++;
    sender()->~QObject();
}

/*!
    \brief Funkcja inicjalizacyjna, przekazująca gałęziom pierwsze lepsze poprawne dane.

    Znajdujemy pierwszą gałęź, która jest zjazdem, następnie w każdej gałęzi kieruję cały ruch na znalezion zjazd.
  */
void QRoundabout::makeBasicTraffic()
{
    int firstOutlet = -1;
    for (int i = 0; i < _numBranches; ++i)
        if ( _branches[i]->getDirection() == outlet)
        {
            firstOutlet = i;  // znajduję pierwszy lepszy zjazd
            break;
        }

    if ((firstOutlet < 0) || (firstOutlet >= _numBranches)) // tymczasowo; opcje nie powinny pozzwalać na brak zjazdów
        firstOutlet = 0;

    foreach (Branch *b, _branches)
        b->setTrafficDistribution(firstOutlet, 100); // w każdej gałęzi kieruję cały ruch na jeden wyjazd
}

/**
  Przebiega po _branches licząc wjazdy i zwraca tą wartość.
  */
int QRoundabout::getNumEntries()
{
    int result = 0;
    foreach (Branch *b, _branches)
        if (b->getDirection() == entry)
            result++;
    return result;
}

/**
  Przebiega po _branches licząc zjazdy i zwraca tą wartość.
  */
int QRoundabout::getNumOutlets()
{
    int result = 0;
    foreach (Branch *b, _branches)
        if (b->getDirection() == outlet)
            result++;
    return result;
}

/**
    Zwraca efektywny kąt odchylenia danej gałęzi (uwzględnia możliwość jej sklejenia).
  */
qreal QRoundabout::getBranchFi(int i)
{
    qreal fi;
    if (_dependences[i] == glued)
        fi = _branches.at(plusModulo(i, 1, _branches.size()))->getFi();
    else
        fi = _branches.at(i)->getFi();

    return fi;
}

qreal QRoundabout::getRimWidth()
{ return (_numLanes * _laneWidth); }

qreal QRoundabout::getRadius()
{ return (_radius); }

qreal QRoundabout::getLaneWidth()
{ return _laneWidth; }

int QRoundabout::getNumLanes()
{ return (_numLanes); }

QGraphicsScene* QRoundabout::getScene()
{ return _scene; }

QList<Branch*>* QRoundabout::getBranches()
{ return &_branches; }

QList<Dependence>* QRoundabout::getDependences()
{ return &_dependences; }

int QRoundabout::getNumLanesOnBranch(int which)
{    return _branches.at(which)->getNumLanes(); }

QList<Barrier*> *QRoundabout::getEntryBarriers()
{ return &_entryBarriers; }

QList<Barrier*> *QRoundabout::getOutletBarriers()
{ return &_outletBarriers; }

QList<Barrier*>* QRoundabout::getChangeLaneBarriers()
{ return &_changeLaneBarriers; }

QList<Barrier*>* QRoundabout::getShortcutBarriers()
{ return &_shortcutBarriers; }

QList<Barrier*>* QRoundabout::getShortcutEndBarriers()
{ return &_shortcutEndBarriers; }

bool QRoundabout::getBarriersVisable()
{   return _barriersVisible;    }

QList<Rails*>* QRoundabout::getRails()
{   return &_rails; }

QRoundabout::~QRoundabout()
{
    qDeleteAll(_branches);
    qDeleteAll(_rails);
    qDeleteAll(_entryBarriers);
    qDeleteAll(_outletBarriers);
    qDeleteAll(_changeLaneBarriers);
    qDeleteAll(_shortcutBarriers);
    delete _scene;
//    qDebug() << "QRoundabout::destructor done";
}


